define(function(require, exports, module) {

    var $ = window.Zepto;

    /**
     * Add to Homescreen v3.0.7
     * @copyright (c) 2014 Matteo Spinelli
     * @license: http://cubiq.org/license
     */

// Check if document is loaded, needed by autostart
    var _DOMReady = false;
    if (document.readyState === 'complete') {
        _DOMReady = true;
    } else {
        window.addEventListener('load', loaded, false);
    }

    function loaded() {
        window.removeEventListener('load', loaded, false);
        _DOMReady = true;
    }

// regex used to detect if app has been added to the homescreen
    var _reSmartURL = /\/ath(\/)?$/;
    var _reQueryString = /([\?&]ath=[^&]*$|&ath=[^&]*(&))/;

// singleton
    var _instance;

    function ath(options) {
        _instance = _instance || new ath.Class(options);

        return _instance;
    }

// message in all supported languages
    ath.intl = {
        en_us: {
            message: 'To add this web app to the home screen: tap %icon and then <strong>%action</strong>.',
            action: {ios: 'Add to Home Screen', android: 'Add to homescreen', windows: 'pin to start'}
        },

        zh_cn: {
            message: '如要把应用程式加至主屏幕,请点击%icon, 然后<strong>%action</strong>',
            action: {ios: '加至主屏幕', android: '加至主屏幕', windows: '按住启动'}
        },

        zh_tw: {
            message: '如要把應用程式加至主屏幕, 請點擊%icon, 然後<strong>%action</strong>.',
            action: {ios: '加至主屏幕', android: '加至主屏幕', windows: '按住啟動'}
        }
    };

// Add 2 characters language support (Android mostly)
    for (var lang in ath.intl) {
        ath.intl[lang.substr(0, 2)] = ath.intl[lang];
    }

// default options
    ath.defaults = {
        appID: 'org.cubiq.addtohome',		// local storage name (no need to change)
        fontSize: 15,				// base font size, used to properly resize the popup based on viewport scale factor
        debug: false,				// override browser checks
        modal: false,				// prevent further actions until the message is closed
        mandatory: false,			// you can't proceed if you don't add the app to the homescreen
        autostart: true,			// show the message automatically
        skipFirstVisit: false,		// show only to returning visitors (ie: skip the first time you visit)
        startDelay: 1,				// display the message after that many seconds from page load
        lifespan: 15,				// life of the message in seconds
        displayPace: 1440,			// minutes before the message is shown again (0: display every time, default 24 hours)
        maxDisplayCount: 0,			// absolute maximum number of times the message will be shown to the user (0: no limit)
        icon: true,					// add touch icon to the message
        message: '',				// the message can be customized
        validLocation: [],			// list of pages where the message will be shown (array of regexes)
        onInit: null,				// executed on instance creation
        onShow: null,				// executed when the message is shown
        onRemove: null,				// executed when the message is removed
        onAdd: null,				// when the application is launched the first time from the homescreen (guesstimate)
        onPrivate: null,			// executed if user is in private mode
        detectHomescreen: false		// try to detect if the site has been added to the homescreen (false | true | 'hash' | 'queryString' | 'smartURL')
    };

// browser info and capability
    var _ua = window.navigator.userAgent;
    var _nav = window.navigator;
    _extend(ath, {
        hasToken: document.location.hash == '#ath' || _reSmartURL.test(document.location.href) || _reQueryString.test(document.location.search),
        isRetina: window.devicePixelRatio && window.devicePixelRatio > 1,
        isIDevice: (/iphone|ipod|ipad/i).test(_ua),
        isMobileChrome: _ua.indexOf('Android') > -1 && (/Chrome\/[.0-9]*/).test(_ua),
        isMobileIE: _ua.indexOf('Windows Phone') > -1,
        language: _nav.language && _nav.language.toLowerCase().replace('-', '_') || ''
    });

// falls back to en_us if language is unsupported
    ath.language = ath.language && ath.language in ath.intl ? ath.language : 'en_us';

    ath.isMobileSafari = ath.isIDevice && _ua.indexOf('Safari') > -1 && _ua.indexOf('CriOS') < 0;
    ath.OS = ath.isIDevice ? 'ios' : ath.isMobileChrome ? 'android' : ath.isMobileIE ? 'windows' : 'unsupported';

    ath.OSVersion = _ua.match(/(OS|Android) (\d+[_\.]\d+)/);
    ath.OSVersion = ath.OSVersion && ath.OSVersion[2] ? +ath.OSVersion[2].replace('_', '.') : 0;

    ath.isStandalone = window.navigator.standalone || ( ath.isMobileChrome && ( screen.height - document.documentElement.clientHeight < 40 ) );	// TODO: check the lame polyfill
    ath.isTablet = (ath.isMobileSafari && _ua.indexOf('iPad') > -1) || (ath.isMobileChrome && _ua.indexOf('Mobile') < 0);

    ath.isCompatible = (ath.isMobileSafari && ath.OSVersion >= 6) || ath.isMobileChrome;	// TODO: add winphone

    var _defaultSession = {
        lastDisplayTime: 0,			// last time we displayed the message
        returningVisitor: false,	// is this the first time you visit
        displayCount: 0,			// number of times the message has been shown
        optedout: false,			// has the user opted out
        added: false				// has been actually added to the homescreen
    };

    ath.removeSession = function(appID) {
        try {
            localStorage.removeItem(appID || ath.defaults.appID);
        } catch (e) {
            // we are most likely in private mode
        }
    };

    ath.Class = function(options) {
        // merge default options with user config
        this.options = _extend({}, ath.defaults);
        _extend(this.options, options);

        // normalize some options
        this.options.mandatory = this.options.mandatory && ( 'standalone' in window.navigator || this.options.debug );
        this.options.modal = this.options.modal || this.options.mandatory;
        if (this.options.mandatory) {
            this.options.startDelay = -0.5;		// make the popup hasty
        }
        this.options.detectHomescreen = this.options.detectHomescreen === true ? 'hash' : this.options.detectHomescreen;

        // setup the debug environment
        if (this.options.debug) {
            ath.isCompatible = true;
            ath.OS = typeof this.options.debug == 'string' ? this.options.debug : ath.OS == 'unsupported' ? 'android' : ath.OS;
            ath.OSVersion = ath.OS == 'ios' ? '7' : '4';
        }

        // the element the message will be appended to
        this.container = document.documentElement;

        // load session
        this.session = JSON.parse(localStorage.getItem(this.options.appID));

        // user most likely came from a direct link containing our token, we don't need it and we remove it
        if (ath.hasToken && ( !ath.isCompatible || !this.session )) {
            ath.hasToken = false;
            _removeToken();
        }

        // the device is not supported
        if (!ath.isCompatible) {
            return;
        }

        this.session = this.session || _defaultSession;

        // check if we can use the local storage
        try {
            localStorage.setItem(this.options.appID, JSON.stringify(this.session));
            ath.hasLocalStorage = true;
        } catch (e) {
            // we are most likely in private mode
            ath.hasLocalStorage = false;

            if (this.options.onPrivate) {
                this.options.onPrivate.call(this);
            }
        }

        // check if this is a valid location
        var isValidLocation = !this.options.validLocation.length;
        for (var i = this.options.validLocation.length; i--;) {
            if (this.options.validLocation[i].test(document.location.href)) {
                isValidLocation = true;
                break;
            }
        }

        // check compatibility with old versions of add to homescreen. Opt-out if an old session is found
        if (localStorage.getItem('addToHome')) {
            this.optOut();
        }

        // critical errors:
        // user opted out, already added to the homescreen, not a valid location
        if (this.session.optedout || this.session.added || !isValidLocation) {
            return;
        }

        // check if the app is in stand alone mode
        if (ath.isStandalone) {
            // execute the onAdd event if we haven't already
            if (!this.session.added) {
                this.session.added = true;
                this.updateSession();

                if (this.options.onAdd && ath.hasLocalStorage) {	// double check on localstorage to avoid multiple calls to the custom event
                    this.options.onAdd.call(this);
                }
            }

            return;
        }

        // (try to) check if the page has been added to the homescreen
        if (this.options.detectHomescreen) {
            // the URL has the token, we are likely coming from the homescreen
            if (ath.hasToken) {
                _removeToken();		// we don't actually need the token anymore, we remove it to prevent redistribution

                // this is called the first time the user opens the app from the homescreen
                if (!this.session.added) {
                    this.session.added = true;
                    this.updateSession();

                    if (this.options.onAdd && ath.hasLocalStorage) {	// double check on localstorage to avoid multiple calls to the custom event
                        this.options.onAdd.call(this);
                    }
                }

                return;
            }

            // URL doesn't have the token, so add it
            if (this.options.detectHomescreen == 'hash') {
                history.replaceState('', window.document.title, document.location.href + '#ath');
            } else if (this.options.detectHomescreen == 'smartURL') {
                history.replaceState('', window.document.title, document.location.href.replace(/(\/)?$/, '/ath$1'));
            } else {
                history.replaceState('', window.document.title, document.location.href + (document.location.search ? '&' : '?' ) + 'ath=');
            }
        }

        // check if this is a returning visitor
        if (!this.session.returningVisitor) {
            this.session.returningVisitor = true;
            this.updateSession();

            // we do not show the message if this is your first visit
            if (this.options.skipFirstVisit) {
                return;
            }
        }

        // we do no show the message in private mode
        if (!ath.hasLocalStorage) {
            return;
        }

        // all checks passed, ready to display
        this.ready = true;

        if (this.options.onInit) {
            this.options.onInit.call(this);
        }

        if (this.options.autostart) {
            this.show();
        }
    };

    ath.Class.prototype = {
        // event type to method conversion
        events: {
            load: '_delayedShow',
            error: '_delayedShow',
            orientationchange: 'resize',
            resize: 'resize',
            scroll: 'resize',
            click: 'remove',
            touchmove: '_preventDefault',
            transitionend: '_removeElements',
            webkitTransitionEnd: '_removeElements',
            MSTransitionEnd: '_removeElements'
        },

        handleEvent: function(e) {
            var type = this.events[e.type];
            if (type) {
                this[type](e);
            }
        },

        show: function(force) {
            // in autostart mode wait for the document to be ready
            if (this.options.autostart && !_DOMReady) {
                setTimeout(this.show.bind(this), 50);
                return;
            }

            // message already on screen
            if (this.shown) {
                return;
            }

            var now = Date.now();
            var lastDisplayTime = this.session.lastDisplayTime;

            if (force !== true) {
                // this is needed if autostart is disabled and you programmatically call the show() method
                if (!this.ready) {
                    return;
                }

                // we obey the display pace (prevent the message to popup too often)
                if (now - lastDisplayTime < this.options.displayPace * 60000) {
                    return;
                }

                // obey the maximum number of display count
                if (this.options.maxDisplayCount && this.session.displayCount >= this.options.maxDisplayCount) {
                    return;
                }
            }

            this.shown = true;

            // increment the display count
            this.session.lastDisplayTime = now;
            this.session.displayCount++;
            this.updateSession();

            // try to get the highest resolution application icon
            if (!this.applicationIcon) {
                if (ath.OS == 'ios') {
                    this.applicationIcon = document.querySelector('head link[rel^=apple-touch-icon][sizes="152x152"],head link[rel^=apple-touch-icon][sizes="144x144"],head link[rel^=apple-touch-icon][sizes="120x120"],head link[rel^=apple-touch-icon][sizes="114x114"],head link[rel^=apple-touch-icon]');
                } else {
                    this.applicationIcon = document.querySelector('head link[rel^="shortcut icon"][sizes="196x196"],head link[rel^=apple-touch-icon]');
                }
            }

            var message = '';

            if (this.options.message in ath.intl) {		// you can force the locale
                message = ath.intl[this.options.message].message.replace('%action', ath.intl[this.options.message].action[ath.OS]);
            } else if (this.options.message !== '') {		// or use a custom message
                message = this.options.message;
            } else {										// otherwise we use our message
                message = ath.intl[ath.language].message.replace('%action', ath.intl[ath.language].action[ath.OS]);
            }

            // add the action icon
            message = '<p>' + message.replace('%icon', '<span class="ath-action-icon">icon</span>') + '</p>';

            // create the message container
            this.viewport = document.createElement('div');
            this.viewport.className = 'ath-viewport';
            if (this.options.modal) {
                this.viewport.className += ' ath-modal';
            }
            if (this.options.mandatory) {
                this.viewport.className += ' ath-mandatory';
            }
            this.viewport.style.position = 'absolute';

            // create the actual message element
            this.element = document.createElement('div');
            this.element.className = 'ath-container ath-' + ath.OS + ' ath-' + ath.OS + (ath.OSVersion + '').substr(0, 1) + ' ath-' + (ath.isTablet ? 'tablet' : 'phone');
            this.element.style.cssText = '-webkit-transition-property:-webkit-transform,opacity;-webkit-transition-duration:0;-webkit-transform:translate3d(0,0,0);transition-property:transform,opacity;transition-duration:0;transform:translate3d(0,0,0);-webkit-transition-timing-function:ease-out';
            this.element.style.webkitTransform = 'translate3d(0,-' + window.innerHeight + 'px,0)';
            this.element.style.webkitTransitionDuration = '0s';

            // add the application icon
            if (this.options.icon && this.applicationIcon) {
                this.element.className += ' ath-icon';
                this.img = document.createElement('img');
                this.img.className = 'ath-application-icon';
                this.img.addEventListener('load', this, false);
                this.img.addEventListener('error', this, false);

                this.img.src = this.applicationIcon.href;
                this.element.appendChild(this.img);
            }

            this.element.innerHTML += message;

            // we are not ready to show, place the message out of sight
            this.viewport.style.left = '-99999em';

            // attach all elements to the DOM
            this.viewport.appendChild(this.element);
            this.container.appendChild(this.viewport);

            // if we don't have to wait for an image to load, show the message right away
            if (!this.img) {
                this._delayedShow();
            }
        },

        _delayedShow: function(e) {
            setTimeout(this._show.bind(this), this.options.startDelay * 1000 + 500);
        },

        _show: function() {
            var that = this;

            // update the viewport size and orientation
            this.updateViewport();

            // reposition/resize the message on orientation change
            window.addEventListener('resize', this, false);
            window.addEventListener('scroll', this, false);
            window.addEventListener('orientationchange', this, false);

            if (this.options.modal) {
                // lock any other interaction
                document.addEventListener('touchmove', this, true);
            }

            // Enable closing after 1 second
            if (!this.options.mandatory) {
                setTimeout(function() {
                    that.element.addEventListener('click', that, true);
                }, 1000);
            }

            // kick the animation
            setTimeout(function() {
                that.element.style.webkitTransform = 'translate3d(0,0,0)';
                that.element.style.webkitTransitionDuration = '1.2s';
            }, 0);

            // set the destroy timer
            if (this.options.lifespan) {
                this.removeTimer = setTimeout(this.remove.bind(this), this.options.lifespan * 1000);
            }

            // fire the custom onShow event
            if (this.options.onShow) {
                this.options.onShow.call(this);
            }
        },

        remove: function() {
            clearTimeout(this.removeTimer);

            // clear up the event listeners
            if (this.img) {
                this.img.removeEventListener('load', this, false);
                this.img.removeEventListener('error', this, false);
            }

            window.removeEventListener('resize', this, false);
            window.removeEventListener('scroll', this, false);
            window.removeEventListener('orientationchange', this, false);
            document.removeEventListener('touchmove', this, true);
            this.element.removeEventListener('click', this, true);

            // remove the message element on transition end
            this.element.addEventListener('transitionend', this, false);
            this.element.addEventListener('webkitTransitionEnd', this, false);
            this.element.addEventListener('MSTransitionEnd', this, false);

            // start the fade out animation
            this.element.style.webkitTransitionDuration = '0.3s';
            this.element.style.opacity = '0';
        },

        _removeElements: function() {
            this.element.removeEventListener('transitionend', this, false);
            this.element.removeEventListener('webkitTransitionEnd', this, false);
            this.element.removeEventListener('MSTransitionEnd', this, false);

            // remove the message from the DOM
            this.container.removeChild(this.viewport);

            this.shown = false;

            // fire the custom onRemove event
            if (this.options.onRemove) {
                this.options.onRemove.call(this);
            }
        },

        updateViewport: function() {
            if (!this.shown) {
                return;
            }

            this.viewport.style.width = window.innerWidth + 'px';
            this.viewport.style.height = window.innerHeight + 'px';
            this.viewport.style.left = window.scrollX + 'px';
            this.viewport.style.top = window.scrollY + 'px';

            var clientWidth = document.documentElement.clientWidth;

            this.orientation = clientWidth > document.documentElement.clientHeight ? 'landscape' : 'portrait';

            var screenWidth = ath.OS == 'ios' ? this.orientation == 'portrait' ? screen.width : screen.height : screen.width;
            this.scale = screen.width > clientWidth ? 1 : screenWidth / window.innerWidth;

            this.element.style.fontSize = this.options.fontSize / this.scale + 'px';
        },

        resize: function() {
            clearTimeout(this.resizeTimer);
            this.resizeTimer = setTimeout(this.updateViewport.bind(this), 100);
        },

        updateSession: function() {
            if (ath.hasLocalStorage === false) {
                return;
            }

            localStorage.setItem(this.options.appID, JSON.stringify(this.session));
        },

        clearSession: function() {
            this.session = _defaultSession;
            this.updateSession();
        },

        optOut: function() {
            this.session.optedout = true;
            this.updateSession();
        },

        optIn: function() {
            this.session.optedout = false;
            this.updateSession();
        },

        clearDisplayCount: function() {
            this.session.displayCount = 0;
            this.updateSession();
        },

        _preventDefault: function(e) {
            e.preventDefault();
            e.stopPropagation();
        }
    };

    // utility
    function _extend(target, obj) {
        for (var i in obj) {
            target[i] = obj[i];
        }

        return target;
    }

    function _removeToken() {
        if (document.location.hash == '#ath') {
            history.replaceState('', window.document.title, document.location.href.split('#')[0]);
        }

        if (_reSmartURL.test(document.location.href)) {
            history.replaceState('', window.document.title, document.location.href.replace(_reSmartURL, '$1'));
        }

        if (_reQueryString.test(document.location.search)) {
            history.replaceState('', window.document.title, document.location.href.replace(_reQueryString, '$2'));
        }
    }

    $.AMUI.addToHomescreen = ath;

    module.exports = ath;
});
